iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 6
0
自我挑戰組

Some thing with Reason系列 第 6 篇

BuckleScript - 物件

  • 分享至 

  • xImage
  •  

Bukle Script Object

Javascript 中的物件有兩個主要的目的

  • 像是一個雜湊表(或是字典), Keys 可以動態的 新增/刪除 ,值是同樣的型態
  • 像是一個紀錄(Record),欄位是固定的(但是也允許是選擇性的),他的值可以是不同的形態

這部分 BuckleScript 的物件也是一樣的

雜湊表模式

直到最近 Javascript 才終於有正確的支援 Map

物件使用上和 Map 可以是一樣的

前提是

  • 可能會或是不會 新增/刪除 參數的鰎值
  • 值可以被 動態/計算 鍵值 瀏覽
  • 值得型態都是一樣

這時候可以利用 Js.Dict  來綁定這個 Javascript 物件

你可以就像 Javascript 中的 Object 來做操作

Js.Dict.keys 來取得所有的鍵值

Js.Divt.values 來取得所有的值

範例

let myMap = Js.Dict.empty();

Js.Dict.set(myMap, "Allison", 10);

[@bs.val] external studentAge: Js.Dict.t(int) = "student";

switch (Js.Dict.get(studentAge, "Joe")) {
| None => Js.log("Joe can't be found")
| Some(age) => Js.log("Joe is " ++ string_of_int(age));
};

轉譯後的結果

'use strict';

var myMap = { };

myMap["Allison"] = 10;

var match = student["Joe"];

if (match !== undefined) {
  console.log("Joe is " + String(match));
} else {
  console.log("Joe can't be found");
}

你可以看到其實 Js.Dict 是基於 Javascript 的物件

整個 API 都沒有使用到 external 所以在編譯之後 整個 API 都會消失

編譯結果中你也找不到 Dict 的相關字

這很便於你將 Js 檔案轉為 BuckleScript 檔案

Record 模式

如果你的 Js 物件:

  • 有固定並且已知的欄位
  • 包含的值有不同的型別

這時候大多數的程式語言中 你都可以使用 Record

例如:

{
  "John": 10,
  "Allison": 20,
  "Jimmy": 15
}

和

{
  name: "John",
  age: 10,
  job: "CEO"
}

的不同

前者會使用 雜湊表 的型態來處理

後者會使用 Record 來處理

而在 BuckleScript 中要使用 bs.deriving abstract 功能

後者則會這樣寫

[@bs.deriving abstract]
type person = {
  name: string,
  age: int,
  job: string
};

[@bs.val] external john: person = "john";

note person 並不是一個 Record 的型別

他只是看起來像是 Record 的型別

而且可以使用 Record 的形態檢查

bs.deriving abstract 就是在註解將 person 轉為 抽象型別

建立

上述的抽象型別因為不是 Record 型別

所以不能直接使用型態的方式來建立

[@bs.deriving abstract]
type person = {
  name: string,
  age: int,
  job: string,
};

let joe = person(~name="Joe", ~age=20, ~job="teacher")

轉譯後的結果

// Generated by BUCKLESCRIPT VERSION 4.0.5, PLEASE EDIT WITH CARE
'use strict';


var joe = {
  name: "Joe",
  age: 20,
  job: "teacher"
};

exports.joe = joe;
/* No side effect */

結果對於運行並不會有成本

欄位重新命名

有時候在綁定 JS object 的時候,欄位名稱在 BuckleScript/Reason 中是無效的名稱

有兩種範例 一個是 {type: "foo"} (BS/Reason 中的保留字) 和 {"aria-checked": true}

可以選擇另一個有效的名稱 使用 @bs.as 來規避這個問題

[@bs.deriving abstract]
type data = {
  [@bs.as "type"] type_: string,
  [@bs.as "aria-checked"] ariaLabel: string
};

let d = data(~type_="message", ~ariaLabel="hello");

輸出會是

'use strict';


var d = {
  type: "message",
  "aria-checked": "hello"
};
可選的標籤

可以建立可忽略的 欄位

[[@bs.deriving abstract]
type person = {
  [@bs.optional] name: string,
  age: int,
  job: string
};

let joe = person(~age=19, ~job="sleep", ());
Js.log(joe);

編譯後會是

var Js_primitive = require("bs-platform/lib/js/js_primitive.js");

function joe(param) {
  return (function (param$1) {
      var prim = param;
      var prim$1 = 19;
      var prim$2 = "sleep";
      var tmp = {
        age: prim$1,
        job: prim$2
      };
      if (prim !== undefined) {
        tmp.name = Js_primitive.valFromOption(prim);
      }
      return tmp;
    });
}

console.log(joe);

可以看到 name 會是可選的,若是沒有也不會造成錯誤

note:bs.optional 只是將 name 改為可選,但是如果輸入 Option(string) 不會有作用

Accessors

當你使用 bs.deriving abstract 隱含了 recode 的型態,你無法使用 joe.age 這樣的方式來取得值

原生會提供 getter 和 setter 來完成這個

取值

每一個 bs.deriving abstract 欄位 都會有一個 getter function

在上面的範例中會有三個 getter function ,nameGet, ageGet, jobGet

他們會取得 person 的值並分別回傳 string, int, string

[@bs.deriving abstract]
type person = {
  [@bs.optional] name: string,
  age: int,
  job: string,
};

let joe = person(~age=20, ~job="teacher", ());

let twenty = ageGet(joe);
Js.log(twenty);

/**
也可以寫成這樣
joe-> ageGet-> Js.log
**/

至於改值則是

[@bs.deriving abstract]
type person = {
  [@bs.optional] name: string,
  mutable age: int,
  job: string,
};

let joe = person(~age=20, ~job="teacher", ());

joe-> ageGet-> Js.log; /* 20 */
joe-> ageSet(21)-> Js.log;
joe-> ageGet-> Js.log; /* 21 */

note: 要記得將需要修改的參數前面加上 mutable

Object Methods

可以附加任何function 在類型上(任何類型的 record 不僅止於 @bs.deriving abtract)

可以參閱 Object Method

這部分之後會再討埨

可變性

Test.re

[@bs.deriving abstract]
type cord = {
  [@bs.optional] mutable x: int,
  y: int,
};

Test.rei

[@bs.deriving abstract]
type cord = {
  [@bs.optional] x: int,
  y: int,
};

Object2

如果上述的 Object 並不符合您的需求

也有另外一種方式可以綁定 Javascript Object

有幾個前提

  • 你不想要預先宣告 type
  • 你希望你的 Object 是有結構性的

Ex: 你的類型希望可以接受所有含有 age 這個參數的物件,而不是只有特定參數的物件

陷阱

note: 不能使用普通的 Reason/OCaml 的物件類型,如下

type person = {
  .
  name: string,
  age: int,
  job: string
};

你仍然可以宣告這個類型

但是無法編譯成功

因為 Reason/OCaml 的對象工作方式是不一樣的

解決方案

BuckleScript 利用 Js.t 來做包裝類型

以便控制和追蹤可以編譯成 Javascript 的對象子集合

type person = Js.t({
  .
  name: string,
  age: int,
  job: string
});

let john = {"name": "john", "age": 11};

john##name -> Js.log;

從現在開始 BuckleScript 都會以 Js.t 來消除跟 一般物件 和 Javascript 物件的歧義

因為 Reason 有包覆一層語法糖 會將 Js.t({. name: string}) 轉為 {. "name": string}

 訪問 與 修改

取值

要取得值的話方法為 ##

範例如下

type person = Js.t({
  .
  name: string,
  age: int,
  job: string
});

let john = {
  "name": "john",
  "age": 11,
  "job": "development"
};

let johnName = john##name;

明天再來談談 Some 和 Option 的問題


上一篇
Reason 的好朋友
下一篇
BasicType - OptionsAndSomeAndNone
系列文
Some thing with Reason 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言